﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;

namespace MkvSplicer.Video
{
    public abstract partial class VideoTrack
    {
        public string Colourspace { get; protected set; }
        public int Bitdepth {get; protected set; }
        public long FrameCount {get { return frames.LongLength; } }
        protected VideoFrame[] frames, gops;

        public long ConvertFrameNumberToTimecode(long frameNumber)
        {
            if (frameNumber < 0 || frameNumber > FrameCount)
            {
                throw new FormatException("Frame number is out of bounds.");
            }

            return frames[frameNumber].timecode;
        }

        //Returns zero-based index
        //O(lg(n))
        public long ConvertToFrameNumber(string time)
        {
            long timecode = Timecodes.ParseFormattedTime(time);
            long start = 0, end = frames.LongLength - 1L;

            while (start <= end)
            {
                long midpt = start + ((end - start) / 2);
                if (frames[midpt].timecode == timecode)
                    return midpt;
                else if (frames[midpt].timecode < timecode)
                    start = midpt + 1;
                else
                    end = midpt - 1;
            }

            long min = Math.Min(start, end);
            long max = Math.Max(start, end);
            if (frames[max].timecode > timecode)
                return min;
            return max;
        }

        //Returns zero-based index
        //O(1)
        public long FindNearestKeyframe(long frameNumber, bool nearestleft)
        {
            if (frameNumber >= FrameCount)
            {
                throw new System.FormatException(
                    "Frame number is greater than total number of frames");
            }

            if (frames[frameNumber].isKeyframe)
                return frameNumber;
            else if (nearestleft)
                return gops[frames[frameNumber].gopnumber].framenumber;

            long rightKeyFrame = Math.Min(gops.LongLength - 1L, 
                                                frames[frameNumber].gopnumber + 1);
            return gops[rightKeyFrame].framenumber;
        }

        //Throws ArgumentException
        public SplitInfo parseSplit(string timeStart, string timeEnd)
        {
            return new SplitInfoImpl(this, timeStart, timeEnd);
        }

        //Throws ArgumentException
        public SplitInfo parseSplit(long frameStart, long frameEnd)
        {
            return new SplitInfoImpl(this, frameStart, frameEnd);
        }

        private class SplitInfoImpl : SplitInfo
        {
            public SplitInfoImpl(VideoTrack parent, string timeStart, string timeEnd) :
                base(parent, timeStart, timeEnd) { }
            public SplitInfoImpl(VideoTrack parent, long frameStart, long frameEnd) :
                base(parent, frameStart, frameEnd) { }
        }

        protected class VideoFrame
        {
            //Both the frame and gop number should start from 0.
            public readonly long timecode, framenumber, gopnumber;
            public readonly bool isKeyframe;

            public VideoFrame(long timecode, long framenumber,
                            long gopnumber, bool isKeyframe)
            {
                this.timecode = timecode;
                this.framenumber = framenumber;
                this.gopnumber = gopnumber;
                this.isKeyframe = isKeyframe;
            }
        } //VideoFrame
    } //VideoTrack
}
